The pre-processor is activated by a '#' character in column one of the source code. There are several statements vis:
#include
#define
#undef
#if
#else
#endif
#ifdef
#ifndef
#pragma
#include.
In the programming examples presented in the previous lessons you will probably have noticed that there is this statement:
#include <stdio.h>
right at the start of the program text. This statement tells the pre-processor to include the named file in the your program text. As far as the compiler is concerned this text appears just as if you had typed it yourself!
This is one of the more useful facilities provided by the 'C' language.
The #include statement is frequently combined with the #if construct.
In this program fragment the file "true.h" is included in your program
if the pre-processor symbol FLAG is true, and "false.h" included if FLAG
is false.
#if ( FLAG )
# include "true.h"
#else
# include "false.h"
#endif
This mechanism has many uses, one of which is to provide
portability between all the 57,000 slightly different versions of unix and also other operating systems. Another use is to be able to alter the way in which your program behaves according to the preference of the user.
Of course, you will be asking the question "Where is the file stored?".
Well, if the filename is delimited by the "<" and ">" characters as in the
example above the file comes from the /usr/include directory, but if the name of the file is delimited by quotes then the file is to be found in your current working directory. (This is not quite the whole truth as 'C' compilers allow you to extend the search path for the include files using command line option switches. - See your compiler manual for the whole story. )
So, I would like to suggest that you to have a look around the /usr/include directory and its /sys sub-directory. You should use either your editor in 'view' mode or the pg utility. This will ensure that you can't have an accident and alter one of the files by mistake if you are slightly silly and just happen to be logged on as the super-user.
A typical file to examine is usr/include/time.h.
It's quite small so here it is.
/* Copyright (c) 1984 AT&T */
/* All Rights Reserved */
/* THIS IS UNPUBLISHED PROPRIETARY SOURCE CODE OF AT&T */
/* The copyright notice above does not evidence any */
/* actual or intended publication of such source code. */
As you can see ( forgetting about the comments and #ident )
there are three different uses for the file.
a) The definition of data structures and types.
b) The declaration of functions which use the data structures.
c) The declaration of of external data objects.
These lines of code are all you need in your program in order to be able to use, in this case, the library routine to access the clock in the computer, but of course the paradigm applies to all programs which are created by one programmer and used by another member of the programming team. Note that, by proxy, or whatever, the author of the library routines has in effect become a member of your programming team.
You might care to write a program or two which use this header file,
and for those who are motivated it might be an idea to re-implement localtime so that it understands Summer Time in the Southern Hemisphere. (!)
Using another totally trivial example in order to get the idea across please examine the hello world program printed immediately below.
and then moves the executable image to a different file and executes it.
Note that more than one command per line can be issued to the shell by
separating the commands with the ';' delimiting character.
Here - Surprise, Surprise - is the output of the second version.
Hello World...
I'd like to suggest that you use your editor to cut these example programs
and the shell file below out of the mail file and have a play with them.
/* ----------------------------------------- */
# @(#) Shell file to do the compilations.
cc -o hello_uc -DHW_H="\"hw_uc.h\"" hello.c
cc -o hello_lc -DHW_H="\"hw_lc.h\"" hello.c
/* ----------------------------------------- */
#define
This statement allows you to set up macro definitions. The word immediately after the #define, together with its arguments, is
expanded in the program text to the whole of the rest of the line.
#define min(a, b) ((a<b) ? a : b )
Some things to note:
1) There isn't a space between the last character of the symbol
being defined and the opening parenthesis enclosing the arguments, and there MUST NOT BE
one.
2) The code into which the macro is expanded MUST always be enclosed in parentheses and for safety always use parentheses
to get the arithmetic right.
3) Never EVER define a macro, and use it with a side effect. e.g.
c = min ( a++, b); /* DON'T _EVER_ DO THIS!!! */
Do you think that the value of 'a' will get advanced after the
macro is used? Well it WON'T. It gets incremented after the less
than test and before the values get assigned! I have written a tiny
program which uses the min macro above. Have a look at the output
from the pre-processor. Lesson One told you how to do this.
Now execute it and get an educative surprise!
/* ----------------------------------------- */
#include <stdio.h>
#define min(a, b) ((a<b) ? a : b )
main()
{ int a,b,c;
a = 1;
b = 2;
c = min ( a++, b); /* DON'T _EVER_ DO THIS!!! */
printf ( "a: %d, b: %d, c: %d\n", a, b, c );
}
/* ----------------------------------------- */
4) You can continue a macro on the next line by putting a \ ( back-slash ) as THE VERY LAST character on the line. NOTHING,
not even a space may follow, as your compiler just can't handle it. I spent far too long trying to find one of those really difficult bugs, and it turned out that this was the problem - spaces are transparent aren't they?
5) Using macros is fast and convenient, but they do take up a lot of memory because the code is expanded and inserted into the output stream for every occurrence of the macro in your code. There is a trade-off between using a macro and a function.
The symbol does not have to be the handle for a macro expansion,
but can just be equated to a single constant. This is done many times over in the header files provided by the operating system vendor.
Have a look in /usr/include/sys/file.h for an example of this.
#undef
Not surprisingly this preprocessor command removes a symbol WHICH IS BEING USED BY THE PRE-PROCESSOR - don't
confuse it with compiler proper symbols.
Note that the symbol can be a macro name, in which case the
space used for the code expansion is made available for re-use.
#if ( FLAG )
/* Code in here is sent on to the compiler if FLAG is true. */
#else
/* Code in here is sent on to the compiler if FLAG is false. */
#endif
When the pre-processor encounters one of these, the lines of code between the #if and the corresponding #else or #endif are either skipped over or allowed to proceed to the compilation phase
depending on the truth or falsity of the logical expression ( FLAG ).
All the logical and boolean expressions availableas part of the 'C' language are available here. You are also allowed to say:
#if defined( FLAG ) or,
#if !defined( FLAG )
The symbol FLAG may be an expression which reduces to a
boolean value.
A convention which is adhered to quite well is that all pre-processor
symbols are in UPPER_CASE so as to make them obvious.
#ifdef FLAG or,
#ifndef FLAG
These two statements are the old fashioned way of testing
whether a symbol is defined or not. They are absolutely the same
as the previous example.
There are two more pre-processor statements, namely the #pragma and the "stringizing" operator. The #pragma is used to alter the way in which the compiler works on a block of code, but it is completely implementation dependant and you must refer to your compiler
manual. I can't help as they are all different. The "stringizing" operator
is quite an advanced technique and will be dealt with later on.